/*
 * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.com
 * The software in this package is published under the terms of the CPAL v1.0
 * license, a copy of which has been included with this distribution in the
 * LICENSE.txt file.
 */
package org.mule.runtime.config.spring.handlers;

import org.mule.runtime.config.spring.MuleHierarchicalBeanDefinitionParserDelegate;
import org.mule.runtime.config.spring.parsers.AbstractChildDefinitionParser;
import org.mule.runtime.config.spring.parsers.DeprecatedBeanDefinitionParser;
import org.mule.runtime.config.spring.parsers.MuleDefinitionParser;
import org.mule.runtime.config.spring.parsers.MuleDefinitionParserConfiguration;
import org.mule.runtime.config.spring.parsers.PostProcessor;
import org.mule.runtime.config.spring.parsers.PreProcessor;
import org.mule.runtime.config.spring.parsers.assembly.BeanAssembler;
import org.mule.runtime.config.spring.parsers.assembly.DefaultBeanAssembler;
import org.mule.runtime.config.spring.parsers.assembly.configuration.ValueMap;
import org.mule.runtime.config.spring.parsers.generic.MuleOrphanDefinitionParser;
import org.mule.runtime.core.util.ClassUtils;
import org.mule.runtime.core.util.IOUtils;

import java.io.InputStream;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import javax.xml.namespace.QName;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
import org.springframework.beans.factory.xml.ParserContext;
import org.w3c.dom.Element;
import org.w3c.dom.Node;


/**
 * This Namespace handler extends the default Spring {@link org.springframework.beans.factory.xml.NamespaceHandlerSupport} to
 * allow certain elements in document to be ignored by the handler.
 */
public abstract class AbstractMuleNamespaceHandler extends NamespaceHandlerSupport {

  public static final String GLOBAL_ENDPOINT = "endpoint";
  public static final String INBOUND_ENDPOINT = "inbound-endpoint";
  public static final String OUTBOUND_ENDPOINT = "outbound-endpoint";

  protected transient final Logger logger = LoggerFactory.getLogger(getClass());

  protected AbstractMuleNamespaceHandler() {
    registerBeanDefinitionParser("annotations", new AnnotationsBeanDefintionParser());
  }

  /**
   * @param name The name of the element to be ignored.
   */
  protected final void registerIgnoredElement(String name) {
    registerBeanDefinitionParser(name, new IgnoredDefinitionParser());
  }

  protected MuleDefinitionParserConfiguration registerConnectorDefinitionParser(Class connectorClass, String transportName) {
    return registerConnectorDefinitionParser(findConnectorClass(connectorClass, transportName));
  }

  protected MuleDefinitionParserConfiguration registerConnectorDefinitionParser(Class connectorClass) {
    return registerConnectorDefinitionParser(new MuleOrphanDefinitionParser(connectorClass, true));
  }

  protected MuleDefinitionParserConfiguration registerConnectorDefinitionParser(MuleDefinitionParser parser) {
    registerBeanDefinitionParser("connector", parser);
    return parser;
  }

  protected MuleDefinitionParserConfiguration registerMuleBeanDefinitionParser(String name, MuleDefinitionParser parser) {
    registerBeanDefinitionParser(name, parser);
    return parser;
  }

  protected MuleDefinitionParserConfiguration registerStandardTransportEndpoints(String protocol, String[] requiredAttributes) {
    return new RegisteredMdps();
  }

  protected MuleDefinitionParserConfiguration registerMetaTransportEndpoints(String protocol) {
    return new RegisteredMdps();
  }

  public static class IgnoredDefinitionParser implements BeanDefinitionParser {

    public IgnoredDefinitionParser() {
      super();
    }

    @Override
    public BeanDefinition parse(Element element, ParserContext parserContext) {
      return null;
    }
  }

  protected class RegisteredMdps implements MuleDefinitionParserConfiguration {

    private Set bdps = new HashSet();

    protected MuleDefinitionParser add(MuleDefinitionParser bdp) {
      bdps.add(bdp);
      return bdp;
    }

    @Override
    public MuleDefinitionParserConfiguration registerPreProcessor(PreProcessor preProcessor) {
      for (Iterator bdp = bdps.iterator(); bdp.hasNext();) {
        ((MuleDefinitionParserConfiguration) bdp.next()).registerPreProcessor(preProcessor);
      }
      return this;
    }

    @Override
    public MuleDefinitionParserConfiguration registerPostProcessor(PostProcessor postProcessor) {
      for (Iterator bdp = bdps.iterator(); bdp.hasNext();) {
        ((MuleDefinitionParserConfiguration) bdp.next()).registerPostProcessor(postProcessor);
      }
      return this;
    }

    @Override
    public MuleDefinitionParserConfiguration addReference(String propertyName) {
      for (Iterator bdp = bdps.iterator(); bdp.hasNext();) {
        ((MuleDefinitionParserConfiguration) bdp.next()).addReference(propertyName);
      }
      return this;
    }

    @Override
    public MuleDefinitionParserConfiguration addMapping(String propertyName, Map mappings) {
      for (Iterator bdp = bdps.iterator(); bdp.hasNext();) {
        ((MuleDefinitionParserConfiguration) bdp.next()).addMapping(propertyName, mappings);
      }
      return this;
    }

    @Override
    public MuleDefinitionParserConfiguration addMapping(String propertyName, String mappings) {
      for (Iterator bdp = bdps.iterator(); bdp.hasNext();) {
        ((MuleDefinitionParserConfiguration) bdp.next()).addMapping(propertyName, mappings);
      }
      return this;
    }

    @Override
    public MuleDefinitionParserConfiguration addMapping(String propertyName, ValueMap mappings) {
      for (Iterator bdp = bdps.iterator(); bdp.hasNext();) {
        ((MuleDefinitionParserConfiguration) bdp.next()).addMapping(propertyName, mappings);
      }
      return this;
    }

    @Override
    public MuleDefinitionParserConfiguration addAlias(String alias, String propertyName) {
      for (Iterator bdp = bdps.iterator(); bdp.hasNext();) {
        ((MuleDefinitionParserConfiguration) bdp.next()).addAlias(alias, propertyName);
      }
      return this;
    }

    @Override
    public MuleDefinitionParserConfiguration addCollection(String propertyName) {
      for (Iterator bdp = bdps.iterator(); bdp.hasNext();) {
        ((MuleDefinitionParserConfiguration) bdp.next()).addCollection(propertyName);
      }
      return this;
    }

    @Override
    public MuleDefinitionParserConfiguration addIgnored(String propertyName) {
      for (Iterator bdp = bdps.iterator(); bdp.hasNext();) {
        ((MuleDefinitionParserConfiguration) bdp.next()).addIgnored(propertyName);
      }
      return this;
    }

    @Override
    public MuleDefinitionParserConfiguration removeIgnored(String propertyName) {
      for (Iterator bdp = bdps.iterator(); bdp.hasNext();) {
        ((MuleDefinitionParserConfiguration) bdp.next()).removeIgnored(propertyName);
      }
      return this;
    }

    @Override
    public MuleDefinitionParserConfiguration setIgnoredDefault(boolean ignoreAll) {
      for (Iterator bdp = bdps.iterator(); bdp.hasNext();) {
        ((MuleDefinitionParserConfiguration) bdp.next()).setIgnoredDefault(ignoreAll);
      }
      return this;
    }

    @Override
    public MuleDefinitionParserConfiguration addBeanFlag(String flag) {
      for (Iterator bdp = bdps.iterator(); bdp.hasNext();) {
        ((MuleDefinitionParserConfiguration) bdp.next()).addBeanFlag(flag);
      }
      return this;
    }
  }

  static class AnnotationsBeanDefintionParser extends AbstractChildDefinitionParser {

    AnnotationsBeanDefintionParser() {
      super();
    }

    @Override
    protected AbstractBeanDefinition parseInternal(Element element, ParserContext context) {
      AbstractBeanDefinition beanDef = super.parseInternal(element, context);
      beanDef.setAttribute(MuleHierarchicalBeanDefinitionParserDelegate.MULE_NO_RECURSE, true);
      beanDef.setAttribute(MuleHierarchicalBeanDefinitionParserDelegate.MULE_NO_REGISTRATION, true);
      return beanDef;
    }

    @Override
    public String getPropertyName(Element element) {
      return "annotation";
    }

    @Override
    protected Class<?> getBeanClass(Element element) {
      return Map.class;
    }

    @Override
    protected void postProcess(ParserContext context, BeanAssembler beanAssembler, Element element) {
      if (beanAssembler instanceof DefaultBeanAssembler) {
        DefaultBeanAssembler assembler = (DefaultBeanAssembler) beanAssembler;

        if (assembler.getTarget() == null) {
          // TODO MULE-9638 - This is possible when the parent node is parsed by the new mechanism
          return;
        }
        if (assembler.isAnnotationsPropertyAvailable(assembler.getTarget().getBeanClassName())) {
          for (Node node = element.getFirstChild(); node != null; node = node.getNextSibling()) {
            if (node.getNodeType() == Node.ELEMENT_NODE) {
              StringBuilder builder = new StringBuilder();
              for (Node child = node.getFirstChild(); child != null; child = child.getNextSibling()) {
                switch (child.getNodeType()) {
                  case Node.TEXT_NODE:
                  case Node.CDATA_SECTION_NODE:
                    builder.append(child.getNodeValue());
                }
              }
              assembler.addAnnotationValue(context.getContainingBeanDefinition().getPropertyValues(),
                                           new QName(node.getNamespaceURI(), node.getLocalName()), builder.toString());
            }
          }
        }
      }
    }
  }

  /**
   * See if there's a preferred connector class
   */
  protected Class findConnectorClass(Class basicConnector, String transportName) {
    String preferredPropertiesURL = "META-INF/services/org/mule/transport/preferred-" + transportName + ".properties";
    InputStream stream = AbstractMuleNamespaceHandler.class.getClassLoader().getResourceAsStream(preferredPropertiesURL);
    if (stream != null) {
      try {
        Properties preferredProperties = new Properties();
        preferredProperties.load(stream);
        String preferredConnectorName = preferredProperties.getProperty("connector");
        if (preferredConnectorName != null) {
          logger.debug("Found preferred connector class " + preferredConnectorName);
          return ClassUtils.getClass(preferredConnectorName);
        }
      } catch (Exception e) {
        logger.debug("Error processing preferred properties", e);
      } finally {
        IOUtils.closeQuietly(stream);
      }
    }
    return basicConnector;
  }

  protected void registerDeprecatedBeanDefinitionParser(String elementName, BeanDefinitionParser parser, String message) {
    registerBeanDefinitionParser(elementName, new DeprecatedBeanDefinitionParser(parser, String
        .format("Schema warning: Use of element <%s> is deprecated.  %s.", elementName, message)));
  }

  protected MuleDefinitionParserConfiguration registerDeprecatedMuleBeanDefinitionParser(String name, MuleDefinitionParser parser,
                                                                                         String message) {
    registerDeprecatedBeanDefinitionParser(name, parser, message);
    return parser;
  }

  protected MuleDefinitionParserConfiguration registerDeprecatedConnectorDefinitionParser(Class connectorClass, String message) {
    MuleOrphanDefinitionParser parser = new MuleOrphanDefinitionParser(connectorClass, true);
    registerDeprecatedBeanDefinitionParser("connector", parser, message);
    return parser;
  }

}
